package com.stanyung.realtimeclient.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.apache.mina.proxy.utils.ByteUtilities;

import com.stanyung.realtimeclient.Packet;
import com.stanyung.realtimeclient.common.ByteUtil;
import com.stanyung.realtimeclient.common.Constants;
import com.stanyung.realtimeclient.common.SocketInvoker;

public class XSocket implements SocketInvoker {

	private Socket socket;
	private InputStream in;
	private OutputStream out;
	private DataReceiveHandler receiveHandler;
	private SocketExceptionHandler exceptionHandler;
	private ReadThread readThread;

	public XSocket(DataReceiveHandler receiveHandler, SocketExceptionHandler exceptionHandler) {
		this.receiveHandler = receiveHandler;
		this.exceptionHandler = exceptionHandler;
	}

	@Override
	public void connect(String hostname, int port) {
		SocketAddress address = new InetSocketAddress(hostname, port);
		socket = new Socket();
		try {
			socket.connect(address, Constants.SOCKET_CONNECT_TIMEOUT);
			in = socket.getInputStream();
			out = socket.getOutputStream();
			readThread = new ReadThread(in);
			readThread.start();
		} catch (IOException e) {
			e.printStackTrace();
			if (readThread != null) {
				readThread.interrupt();
			}
			closeOutputStreamIfNeed(out);
			closeInputStreamIfNeed(in);
			closeSocketIfNeed(socket);
			exceptionHandler.handleConnectError(e);
		}

	}

	class ReadThread extends Thread {

		private InputStream in;
		private boolean canceled = false;

		public ReadThread(InputStream in) {
			this.in = in;
		}

		private void read() throws IOException {
			byte[] buff = new byte[20];
			int readCount;
			while (!canceled) {
				System.out.println("read......");
				if ((readCount = in.read(buff)) > 0) {
					byte[] readByte = new byte[readCount];
					System.arraycopy(buff, 0, readByte, 0, readCount);
					receiveHandler.onReceived(readByte);
				}
			}

		}

		@Override
		public void run() {
			try {
				read();
			} catch (IOException e) {
				e.printStackTrace();
				exceptionHandler.handleReadError(e);
			}
		}

		public void cancel() {
			canceled = true;
		}

	}

	public void send(byte[] data) {
		try {
			byte[] content = new byte[4 + data.length];
			System.arraycopy(ByteUtil.int2Bytes(data.length), 0, content, 0, 4);
			System.arraycopy(data, 0, content, 4, data.length);
			out.write(content);
		} catch (IOException e) {
			e.printStackTrace();
			exceptionHandler.handleWriteError(e, data);
		}
	}

	@Override
	public void disconnect() {
		if (readThread != null) {
			readThread.cancel();
		}
		closeSocketIfNeed(socket);
	}

	private static void closeInputStreamIfNeed(InputStream in) {
		if (in != null) {
			try {
				in.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}

	private static void closeOutputStreamIfNeed(OutputStream out) {
		if (out != null) {
			try {
				out.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}

	private static void closeSocketIfNeed(Socket socket) {
		if (socket != null) {
			try {
				socket.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}

}
